package com.mavenq.fly.websocket.service;

import com.mavenq.fly.websocket.entity.WSMessage;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.web.socket.WebSocketSession;

import java.io.IOException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

@Slf4j
@Service
public class WSConnectionService {

    // token -> session
    private final Map<String, WebSocketSession> tokenSessions = new ConcurrentHashMap<>();
    // userId -> session（业务单播用）
    private final Map<String, WebSocketSession> userSessions = new ConcurrentHashMap<>();

    private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(2);

    public WSConnectionService() {
        // 心跳：每300秒随机抽查在线会话发ping
        scheduler.scheduleAtFixedRate(this::pingRandomSessions, 30, 300, TimeUnit.SECONDS);
    }

    public void register(String token, WebSocketSession session) {
        tokenSessions.put(token, session);
        log.info("Token注册成功：{}，当前在线token数：{}", token, tokenSessions.size());
    }

    public void bindUserId(String token, String userId, WebSocketSession session) {
        unbindUserId(userId); // 先解绑旧绑定
        userSessions.put(userId, session);
        log.info("用户绑定成功：token={} -> userId={}，当前在线用户数：{}", token, userId, userSessions.size());
    }

    public void unbindUserId(String userId) {
        WebSocketSession removed = userSessions.remove(userId);
        if (removed != null) {
            log.info("用户解绑：userId={}", userId);
        }
    }

    public void unregister(String token) {
        WebSocketSession removed = tokenSessions.remove(token);
        if (removed != null) {
            // 同时清理该token绑定的用户
            userSessions.entrySet().removeIf(e -> e.getValue().equals(removed));
            log.info("Token注销：{}，剩余在线token数：{}", token, tokenSessions.size());
        }
    }

    public WebSocketSession getByToken(String token) {
        return tokenSessions.get(token);
    }

    public WebSocketSession getByUserId(String userId) {
        return userSessions.get(userId);
    }

    public void broadcast(WSMessage msg) {
        tokenSessions.values().forEach(s -> send0(s, msg));
    }

    public void sendToUser(String userId, WSMessage msg) {
        WebSocketSession s = userSessions.get(userId);
        if (s != null) send0(s, msg);
    }

    private void send0(WebSocketSession s, WSMessage msg) {
        try {
            if (s.isOpen()) {
                s.sendMessage(new org.springframework.web.socket.TextMessage(toJson(msg)));
            }
        } catch (IOException e) {
            log.warn("发送消息失败：sessionId={}, userId={}", s.getId(), getUserId(s), e);
        }
    }

    private String getUserId(WebSocketSession s) {
        return userSessions.entrySet().stream()
                .filter(e -> e.getValue().equals(s))
                .map(Map.Entry::getKey)
                .findFirst()
                .orElse(null);
    }

    // 简易JSON序列化（生产可用Jackson/Gson替换）
    private String toJson(Object o) {
        try {
            return new com.fasterxml.jackson.databind.ObjectMapper().writeValueAsString(o);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    // 随机抽查会话发ping（触发框架自动pong）
    private void pingRandomSessions() {
        log.info("websocket随机抽查会话发ping...");
        if (tokenSessions.isEmpty()) return;
        tokenSessions.keySet().stream()
                .skip((long) (tokenSessions.size() * Math.random()))
                .findFirst()
                .ifPresent(token -> {
                    WebSocketSession s = tokenSessions.get(token);
                    if (s != null && s.isOpen()) {
                        try {
                            s.sendMessage(new org.springframework.web.socket.PingMessage());
                            log.debug("发送Ping到：{}", s.getId());
                        } catch (IOException e) {
                            log.warn("Ping失败：{}", s.getId(), e);
                        }
                    }
                });
    }
}